#!/usr/bin/env bash
set -euo pipefail

dn=$(dirname "$0")
# shellcheck source=src/cmdlib.sh
. "${dn}"/cmdlib.sh

# This script is used for creating both the bare metal and the canonical VM
# image (qemu). `buildextend-qemu` is a symlink to `buildextend-metal`.
case "$(basename "$0")" in
    "cmd-buildextend-metal") image_type=metal;;
    "cmd-buildextend-metal4k") image_type=metal4k;;
    "cmd-buildextend-dasd") image_type=dasd;;
    "cmd-buildextend-qemu") image_type=qemu;;
    *) fatal "called as unexpected name $0";;
esac

print_help() {
    cat 1>&2 <<EOF
Usage: coreos-assembler buildextend-${image_type} --help
       coreos-assembler buildextend-${image_type} [--build ID]

  Build a bare metal image.
EOF
}

# Parse options
rc=0
build=
options=$(getopt --options h --longoptions help,build: -- "$@") || rc=$?
[ $rc -eq 0 ] || {
    print_help
    exit 1
}
eval set -- "$options"
while true; do
    case "$1" in
        -h | --help)
            print_help
            exit 0
            ;;
        --build)
            build=$2
            shift
            ;;
        --)
            shift
            break
            ;;
        -*)
            fatal "$0: unrecognized option: $1"
            exit 1
            ;;
        *)
            break
            ;;
    esac
    shift
done

if [ $# -ne 0 ]; then
    print_help
    fatal "Too many arguments passed"
fi

case "$basearch" in
    "x86_64") ;;
    *) fatal "$basearch is not supported for this command" ;;
esac

if [[ "$basearch" != "s390x" && $image_type == dasd ]]; then
    fatal "$basearch is not supported for building dasd images"
fi

export LIBGUESTFS_BACKEND=direct
export IMAGE_TYPE="${image_type}"
prepare_build

if [ -z "${build}" ]; then
    build=$(get_latest_build)
    if [ -z "${build}" ]; then
        fatal "No build found."
    fi
fi

builddir=$(get_build_dir "$build")
if [ ! -d "${builddir}" ]; then
    fatal "Build dir ${builddir} does not exist."
fi

# add building sempahore
build_semaphore="${builddir}/.${image_type}.building"
if [ -e "${build_semaphore}" ]; then
    fatal "${build_semaphore} found: another process is building ${image_type}"
fi
touch "${build_semaphore}"
trap 'rm -rf ${build_semaphore}' EXIT

# check if the image already exists in the meta.json
meta_img=$(meta_key "images.${image_type}.path")
if [ "${meta_img}" != "None" ]; then
    echo "${image_type} image already exists:"
    echo "$meta_img"
    exit 0
fi

# reread these values from the build itself rather than rely on the ones loaded
# by prepare_build since the config might've changed since then
name=$(meta_key name)
ref=$(meta_key ref)
if [ "${ref}" = "None" ]; then
    ref=""
fi
commit=$(meta_key ostree-commit)

ostree_repo=${tmprepo}
rev_parsed=$(ostree rev-parse --repo="${ostree_repo}" "${build}" 2>/dev/null || :)
if [ "${rev_parsed}" != "${commit}" ]; then
    # Probably an older commit or tmp/ was wiped. Let's extract it to a separate
    # temporary repo (not to be confused with ${tmprepo}...) so we can feed it
    # as a ref (if not temp) to create_disk.
    echo "Cache for build ${build} is gone"
    echo "Importing commit ${commit} into temporary OSTree repo"
    mkdir -p tmp/repo
    commit_tar_name=$(jq -r .images.ostree.path < "${builddir}/meta.json")
    if [ "${commit_tar_name}" = null ]; then
        commit_tar_name=ostree-commit.tar
    fi
    tar -C tmp/repo -xf "${builddir}/${commit_tar_name}"
    ostree_repo=$PWD/tmp/repo
fi

image_format=raw
if [[ $image_type == qemu ]]; then
    image_format=qcow2
fi

img=${name}-${build}-${image_type}.${basearch}.${image_format}
path=${PWD}/${img}

ignition_platform_id="${image_type}"
# dasd and metal4k are different disk formats, but they're still metal
if [ "${image_type}" = dasd ] || [ "${image_type}" = metal4k ]; then
    ignition_platform_id=metal
fi

yaml2json() {
    python3 -c 'import sys, json, yaml; json.dump(yaml.safe_load(sys.stdin), sys.stdout)' < "$1" > "$2"
}

# Convert the image.yaml to JSON so that it can be more easily parsed
# by the shell script in create_disk.sh.
yaml2json "$configdir/image.yaml" image-config.json
yaml2json "/usr/lib/coreos-assembler/image-default.yaml" image-default.json
# Combine with the defaults
cat image-default.json image-config.json | jq -s add > image-configured.json

# We do some extra handling of the rootfs here; it feeds into size estimation.
rootfs_type=$(jq -re .rootfs < image-configured.json)

# fs-verity requires block size = page size. We need to take that into account
# in the disk size estimation due to higher fragmentation on larger blocks.
BLKSIZE=""
if [ "${rootfs_type}" = "ext4verity" ]; then
    BLKSIZE="$(getconf PAGE_SIZE)"
fi

echo "Estimating disk size..."
# The additional 35% here is obviously a hack, but we can't easily completely fill the filesystem,
# and doing so has apparently negative performance implications.
/usr/lib/coreos-assembler/estimate-commit-disk-size ${BLKSIZE:+--blksize ${BLKSIZE}} --repo "$ostree_repo" "$commit" --add-percent 35 > "$PWD/tmp/ostree-size.json"
rootfs_size="$(jq '."estimate-mb".final' "$PWD/tmp/ostree-size.json")"
# extra size is the non-ostree partitions, see create_disk.sh
image_size="$(( rootfs_size + 513 ))M"
echo "Disk size estimated to ${image_size}"

disk_args=()

# For bare metal and dasd images, we use the estimated image size. For IaaS/virt, we get it from
# image.yaml because we want a "default" disk size that has some free space.
case "${image_type}" in
    metal*|dasd)
        # Unset the root size, which will inherit from the image size
        rootfs_size=0
        ;;
    qemu)
        image_size="$(python3 -c 'import sys, yaml; print(yaml.safe_load(sys.stdin)["size"])' < "$configdir/image.yaml")G"
        rootfs_size="${rootfs_size}M"
        ;;
    *) fatal "unreachable image_type ${image_type}";;
esac

if [ "${image_type}" == metal4k ]; then
    disk_args+=("--no-x86-bios-bootloader")
fi

set -x
kargs="$(python3 -c 'import sys, yaml; args = yaml.safe_load(sys.stdin).get("extra-kargs", []); print(" ".join(args))' < "$configdir/image.yaml")"
tty="console=tty0 console=${DEFAULT_TERMINAL},115200n8"
# On each s390x hypervisor, a tty would be automatically detected by the kernel
# and systemd, there is no need to specify one. However, we keep DEFAULT_TERMINAL
# as ttysclp0, which is helpful for building/testing with KVM+virtio (cmd-run).
# For aarch64, ttyAMA0 is used as the default console
case "$basearch" in
    "aarch64"|"s390x") tty= ;;
esac
kargs="$kargs $tty ignition.platform.id=$ignition_platform_id"

qemu-img create -f ${image_format} "${path}.tmp" "${image_size}"

extra_target_device_opts=""
# we need 4096 block size for ECKD DASD and (obviously) metal4k
if [[ $image_type == dasd || $image_type == metal4k ]]; then
  extra_target_device_opts=",physical_block_size=4096,logical_block_size=4096"
fi
target_drive=("-drive" "if=none,id=target,format=${image_format},file=${path}.tmp,cache=unsafe" \
              "-device" "virtio-blk,serial=target,drive=target${extra_target_device_opts}")

# Generate the JSON describing the disk we want to build
cat >image-dynamic.json << EOF
{
    "rootfs-size": "${rootfs_size}",
    "osname": "${name}",
    "buildid": "${build}",
    "imgid": "${img}",
    "ostree-commit": "${commit}",
    "ostree-ref": "${ref}",
    "ostree-repo": "${ostree_repo}"
}
EOF
cat image-configured.json image-dynamic.json | jq -s add > image.json
runvm "${target_drive[@]}" -- \
        /usr/lib/coreos-assembler/create_disk.sh \
            --config "$(pwd)"/image.json \
            --kargs "\"${kargs}\"" \
            "${disk_args[@]}"
/usr/lib/coreos-assembler/finalize-artifact "${path}.tmp" "${path}"

sha256=$(sha256sum_str < "${img}")
# there's probably a jq one-liner for this...
python3 -c "
import sys, json
j = json.load(sys.stdin)
j['images']['${image_type}'] = {
    'path': '${img}',
    'sha256': '${sha256}',
    'size': $(stat -c '%s' "${img}")
}
json.dump(j, sys.stdout, indent=4)
" < "${builddir}/meta.json" | jq -s add > "meta.json.new"

# and now the crucial bits
cosa meta --workdir "${workdir}" --build "${build}" --artifact "${image_type}" --artifact-json "$(readlink -f meta.json.new)"
/usr/lib/coreos-assembler/finalize-artifact "${img}" "${builddir}/${img}"

# Quiet for the rest of this so the last thing we see is a success message
set +x
# clean up the tmpild
rm -rf "${tmp_builddir}"
echo "Successfully generated: ${img}"
